package net.pulseproject.router.app.view;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

import net.pulseproject.commons.gui.ActionListenerX;
import net.pulseproject.commons.gui.IconsCache;
import net.pulseproject.commons.midi.entity.MidiSocket;
import net.pulseproject.commons.misc.binder.Binder;
import net.pulseproject.router.app.model.Model;
import net.pulseproject.router.app.model.ModelField;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

public class SoloPanel extends JPanel {
	
	private static final long serialVersionUID = 711793697864069320L;

	private static final Log LOG = LogFactory.getLog(SoloPanel.class);
	
	private final JFrame parent;
	
	private final transient Model model;
	
	// LUXURY no, dont use model, but rather use setter method
	// (view should not have any reference to model)
	
	private Map<String, MidiSocket> swingJustSucks;

	private final IconsCache iconsCache;
	
	// TODO write proper system test for solo feature
	
	@Inject
	public SoloPanel(final IconsCache iconsCache, final Model model, @Assisted final JFrame parent) {
		this.iconsCache = iconsCache;
		if(parent == null) { throw new IllegalArgumentException("parent == null"); }
		this.parent = parent;
		if(model == null) { throw new IllegalArgumentException("model == null"); }
		this.model = model;
		
		this.model.addBinding(new Binder<ModelField, Collection<MidiSocket>>() {
			@Override public ModelField getField() {
				return ModelField.ACTIVE_OUTGOING_SOCKETS;
			}
			@SuppressWarnings("unchecked")
			@Override public void onValueSet(final Object value) {
				SoloPanel.this.onActiveOutgoingSocketsSet((Collection<MidiSocket>) value);
			}
		});
		
		this.setOpaque(false);
		this.setPreferredSize(new Dimension(parent.getWidth(), 35));
	}
	
	private void onActiveOutgoingSocketsSet(final Collection<MidiSocket> sockets) {
		LOG.debug("onActiveOutgoingSocketsSet(sockets=" + sockets + ")");
		this.removeAll();
		
		if(sockets == null) {
			this.add(new JLabel("No active routing"));
			this.swingJustSucks = null;
		} else {
			LOG.trace("sockets.size=" + sockets.size());
			
			this.swingJustSucks = new LinkedHashMap<String, MidiSocket>(sockets.size() + 1);
			this.swingJustSucks.put("- solo disabled -", NoMidiSocket.getInstance());
			for (final MidiSocket currentSocket : sockets) {
				this.swingJustSucks.put(currentSocket.formatToLabel(), currentSocket);
			}
			List<String> comboBoxLabels = this.createSoloLabels();
			
			final JComboBox combo = new JComboBox(comboBoxLabels.toArray());
			combo.setMaximumRowCount(Math.min(comboBoxLabels.size(), 10));
			combo.addActionListener(new ActionListenerX(this.iconsCache, this.parent, "Soloing MidiSocket failed!") {
				@Override protected void action(final ActionEvent event) {
					SoloPanel.this.onMidiSocketSelected(event);
				}
			});
			this.add(new JLabel("Solo output: "));
			this.add(combo);
		}
		
		this.revalidate();
		this.repaint();
	}
	
	private List<String> createSoloLabels() {
		List<MidiSocket> socketsSorted= new ArrayList<MidiSocket>(this.swingJustSucks.size());
		socketsSorted.addAll(this.swingJustSucks.values());
		Collections.sort(socketsSorted, MidiSocket.LABEL_COMPARATOR);
		
		List<String> soloLabels = new ArrayList<String>(this.swingJustSucks.size());
		for (MidiSocket midiSocket : socketsSorted) {
			soloLabels.add(midiSocket.formatToLabel());
		}
		return soloLabels;
	}
	
	private void onMidiSocketSelected(final ActionEvent event) {
		final JComboBox combo = (JComboBox) event.getSource();
		final String label = (String) combo.getSelectedItem();
		final MidiSocket selectedSocket = this.swingJustSucks.get(label);
		
		final MidiSocket newSoloedSocket;
		if(selectedSocket == null) {
			throw new RuntimeException("Could not find MidiSocket by label [" + label + "]! " +
					"Only available labels are: " + Arrays.toString(this.swingJustSucks.keySet().toArray()));
			
		} else if(selectedSocket instanceof NoMidiSocket) {
			newSoloedSocket = null;
			
		} else {
			newSoloedSocket = selectedSocket;
		}
		
		this.model.setCurrentSoloedSocket(newSoloedSocket);
	}
	
	
	private static final class NoMidiSocket extends MidiSocket {
		
		private static final NoMidiSocket INSTANCE = new NoMidiSocket();
		
		private NoMidiSocket() {
			super(" NoMidiSocket ", Integer.valueOf(0));
		}

		public static MidiSocket getInstance() {
			return NoMidiSocket.INSTANCE;
		}

		@Override public String formatToLabel() {
			return "- solo disabled -";
		}
	}
	
}
